#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/of.h>

MODULE_LICENSE("GPL");

static int minor = 0; /* 次设备号 */
static dev_t csdn_dev;
static struct cdev csdn_cdev;
static struct class *csdn_class = NULL;
static struct device *csdn_device = NULL;

static int csdn_open(struct inode *inode, struct file *filp)
{
    /* TODO */
    return 0;
}

static int csdn_release(struct inode *inode, struct file *filp)
{
    /* TODO */
    return 0;
}

static ssize_t csdn_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    /* TODO */
    return 0;
}

static ssize_t csdn_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    /* TODO */
    return 0;
}

static struct file_operations csdn_fops = {
    .owner = THIS_MODULE,
    .open = csdn_open,
    .release = csdn_release,
    .read = csdn_read,
    .write = csdn_write,
};

/* 创建设备号 */
static int csdn_register_chrdev(void)
{
    int result;

    result = alloc_chrdev_region(&csdn_dev, minor, 1, "csdn_dev");
    if (result < 0) {
        pr_err("alloc_chrdev_region failed! result: %d\n", result);
        return result;
    }

    return 0;
}

/* 注册驱动 */
static int csdn_cdev_add(void)
{
    int result;

    cdev_init(&csdn_cdev, &csdn_fops);
    csdn_cdev.owner = THIS_MODULE;

    result = cdev_add(&csdn_cdev, csdn_dev, 1);
    if (result < 0) {
        pr_err("alloc_chrdev_region failed! result: %d\n", result);
        unregister_chrdev_region(csdn_dev, 1);
        return result;
    }

    return 0;
}

/* 创建设备节点 */
static int csdn_device_create(void)
{
    csdn_class = class_create(THIS_MODULE, "csdn_dev_class");
    if (IS_ERR(csdn_class)) {
        pr_err("class_create failed!\n");
        goto class_create_fail;
    }

    csdn_device = device_create(csdn_class, NULL, csdn_dev, NULL, "csdn_dev");
    if (IS_ERR(csdn_device)) {
        pr_err("device_create failed!\n");
        goto device_create_fail;
    }

    // pr_err("of_node->name: %s\n", csdn_device->of_node->name);

    return 0;

device_create_fail:
    class_destroy(csdn_class);
class_create_fail:
    cdev_del(&csdn_cdev);
    unregister_chrdev_region(csdn_dev, 1);
    return -1;
}

static int csdn_probe(struct platform_device *csdn_driver)
{
    int result;
    pr_info("csdn_probe\n");

    result = csdn_register_chrdev();
    if (result < 0) {
        return result;
    }

    result = csdn_cdev_add();
    if (result < 0) {
        return result;
    }

    result = csdn_device_create();
    if (result < 0) {
        return result;
    }

    return result;
}

static int csdn_remove(struct platform_device *csdn_driver)
{
    pr_info("csdn_remove\n");
    device_destroy(csdn_class, csdn_dev);
    class_destroy(csdn_class);
    cdev_del(&csdn_cdev);
    unregister_chrdev_region(csdn_dev, 1);

    return 0;
}

struct platform_driver csdn_driver={
	.probe = csdn_probe,
	.remove = csdn_remove,
    .driver = {
        .name = "csdn_blog",
        .owner = THIS_MODULE,
    },
};

static __init int csdn_driver_init(void)
{
    pr_info("csdn_driver_init\n");
	platform_driver_register(&csdn_driver);
    return 0;
}

static __exit void csdn_driver_exit(void)
{
    pr_info("csdn_driver_exit\n");
	platform_driver_unregister(&csdn_driver);
}

module_init(csdn_driver_init);
module_exit(csdn_driver_exit);